package org.snake.exam.eg.clh;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

public class ClhSpinLock implements Lock {

    /**
     * 前驱节点 每个线程
     */
    private final ThreadLocal<Node> prev = ThreadLocal.withInitial(Node::new);

    /**
     * 当前节点
     */
    private final ThreadLocal<Node> currentThreadNode = ThreadLocal.withInitial(Node::new);

    /**
     * 指向队列末尾节点
     * <p>
     * 值得注意 这个节点的默认值是false 也就是说 如果前驱节点是这个默认的节点 那么它是不会起到锁的作用
     * 即 第一个线程进来 执行lock()操作之后 立即返回
     * <p>
     * 还有所有就是 所有的线程都共享这个tail引用
     * 链表推进是依靠这个共享的tail
     */
    private final AtomicReference<Node> tail = new AtomicReference<>(new Node());

    public ClhSpinLock() {
    }

    /**
     * 1.初始状态 tail指向一个node(head)节点
     * +------+
     * | head | <---- tail
     * +------+
     * <p>
     * 2.lock-thread加入等待队列: tail指向新的Node，同时Prev指向tail之前指向的节点
     * +----------+
     * | Thread-A |
     * | := Node  | <---- tail
     * | := Prev  | -----> +------+
     * +----------+        | head |
     * +------+
     * <p>
     * +----------+            +----------+
     * | Thread-B |            | Thread-A |
     * tail ---->  | := Node  |     -->    | := Node  |
     * | := Prev  | ----|      | := Prev  | ----->  +------+
     * +----------+            +----------+         | head |
     * +------+
     * 3.寻找当前node的prev-node然后开始自旋
     */
    @Override
    public void lock() {
        // 值得注意的细节
        // 如果是第一个线程进来
        // 那么pred.locked是false 所以这个线程调用lock之后就直接返回了

        // 第二个线程进来之后 它的pred就是第一个线程的
        // 之后的线程以此类推

        final Node currentThreadNode = this.currentThreadNode.get();
        currentThreadNode.locked = true;

        // 所有线程都通过这个tail的不断变换去推进
        final Node prev = this.tail.getAndSet(currentThreadNode);
        // 这个只是满足每个线程自己prev的设置 其实是可以省略掉的
        this.prev.set(prev);

        // 自旋
        while (prev.locked) {

        }
    }

    @Override
    public void unlock() {
        final Node node = this.currentThreadNode.get();
        node.locked = false;

        this.currentThreadNode.set(this.prev.get());
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public boolean tryLock() {
        return false;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }

    @Override
    public Condition newCondition() {
        return null;
    }

    private static class Node {
        private volatile boolean locked;
    }
}
